#include "EglImpl.h"
#include <android/native_window_jni.h>
#include <RendererCommon.h>

namespace simple_renderer{

EglImpl::EglImpl():_display(EGL_NO_DISPLAY),
                         _context(EGL_NO_CONTEXT),
                         _surface(EGL_NO_SURFACE),
                         _window(nullptr),
                         _width(0),
                         _height(0),
                         _is_env_released(true){

}

EglImpl::~EglImpl(){

    LOGV("[AndroidEgl] ~EglImpl _window=%p \n", _window);
    if(_window){
        ANativeWindow_release(_window);
        _window = nullptr;
    }
}

int EglImpl::InitGLEnv(){

    if(!_window){
        LOGE("[AndroidEgl] set surface first !!!\n");
        return -1;
    }

    LOGV("[EGL] egl error create ret=%0x\n", eglGetError());

    EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
    if (display == EGL_NO_DISPLAY) {
        LOGE("[EGL] eglGetDisplay failed\n");
        return -1;
    }

    EGLint major, minor;
    if (!eglInitialize(display, &major, &minor)) {
        LOGE("[EGL] eglInitialize failed\n");
        return -1;
    }
    LOGI("[EGL] eglInitialize %d.%d\n", (int)major, (int)minor);

    static const EGLint configAttribs[] = {
            EGL_RENDERABLE_TYPE,    EGL_OPENGL_ES2_BIT,
            EGL_SURFACE_TYPE,       EGL_WINDOW_BIT,
            EGL_BLUE_SIZE,          8,
            EGL_GREEN_SIZE,         8,
            EGL_RED_SIZE,           8,
            EGL_NONE
    };

    static const EGLint contextAttribs[] = {
            EGL_CONTEXT_CLIENT_VERSION, 2,
            EGL_NONE
    };

    EGLConfig config;
    EGLint numConfig;
    if (!eglChooseConfig(display, configAttribs, &config, 1, &numConfig)) {
        LOGE("[EGL] eglChooseConfig failed\n");
        eglTerminate(display);
        return -1;
    }

#ifdef __ANDROID__
    {
        EGLint native_visual_id = 0;
        if (!eglGetConfigAttrib(display, config, EGL_NATIVE_VISUAL_ID, &native_visual_id)) {
            LOGE("[EGL] eglGetConfigAttrib() returned error %d", eglGetError());
            eglTerminate(display);
            return -1;
        }

        int32_t width  = ANativeWindow_getWidth(_window);
        int32_t height = ANativeWindow_getWidth(_window);
        LOGI("[EGL] ANativeWindow_setBuffersGeometry(f=%d) width=%d height=%d", native_visual_id, width, height);
        int ret = ANativeWindow_setBuffersGeometry(_window, width, height, native_visual_id);
        if (ret) {
            LOGE("[EGL] ANativeWindow_setBuffersGeometry(format) returned error %d", ret);
            eglTerminate(display);
            return -1;
        }
    }
#endif

    EGLSurface surface = eglCreateWindowSurface(display, config, _window, NULL);
    if (surface == EGL_NO_SURFACE) {
        LOGE("[EGL] eglCreateWindowSurface failed ret=%0x\n", eglGetError());
        eglTerminate(display);
        return -1;
    }

    EGLContext ctx = eglGetCurrentContext();
    LOGE("[EGL] eglCreateContext ctx=%p", ctx);

    EGLContext context = eglCreateContext(display, config, EGL_NO_CONTEXT, contextAttribs);
    if (context == EGL_NO_CONTEXT) {
        LOGE("[EGL] eglCreateContext failed ret=%0x\n", eglGetError());
        eglDestroySurface(display, surface);
        eglTerminate(display);
        return -1;
    }

    if (!eglMakeCurrent(display, surface, surface, context)) {
        LOGE("[EGL] elgMakeCurrent() failed (new) ret=%0x\n", eglGetError());
        eglDestroyContext(display, context);
        eglDestroySurface(display, surface);
        eglTerminate(display);
        return -1;
    }

    this->_context = context;
    LOGE("[EGL] eglCreateContext test_context _context=%p", _context);
    this->_surface = surface;
    this->_display = display;

    _is_env_released = false;

    LOGV("[EGL] egl create\n");

    return 0;
}

int EglImpl::Release(){

//    if(!IsEglValidate()){
//        LOGE("[EGL] egl release error egl is not validate\n");
//        return 0;
//    }

//#ifdef __ANDROID__
//    if(_window){
//        ANativeWindow_release(_window);
//        _window = nullptr;
//    }
//#endif

    //LOGE("[EGL] Release check1 error %0x", eglGetError());
    EGLBoolean ret = EGL_FALSE;
    if(_display){
//        EGLBoolean ret = eglMakeCurrent(_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
//        if (_surface){
//            ret = eglDestroySurface(_display, _surface);
//            LOGV("[EGL] egl destory surface ret=%d\n", ret);
//        }

        LOGV("[EGL] egl release start\n", ret);

        if (_context){
            ret = eglDestroyContext(_display, _context);
            LOGV("[EGL] egl destory context ret=%d test_context _context=%p\n", ret, _context);
        }

        ret = eglReleaseThread();
        if(!ret){
            LOGV("[EGL] egl destory eglReleaseThread ret=%d\n", ret);
        }

        ret = eglTerminate(_display);
        if(!ret){
            LOGV("[EGL] egl destory eglTerminate ret=%d\n", ret);
        }

//        ret = eglReleaseThread(); // FIXME: call at thread exit ???
//        if(!ret){
//            LOGV("[EGL] egl destory eglReleaseThread ret=%d\n", ret);
//        }
    }

//#ifdef __ANDROID__
//    if(_window){
//        ANativeWindow_release(_window);
//        _window = nullptr;
//    }
//#endif

//    if(_window){
//        ANativeWindow_release(_window);
//        _window = nullptr;
//    }

    _context = EGL_NO_CONTEXT;
    _surface = EGL_NO_SURFACE;
    _display = EGL_NO_DISPLAY;

    _is_env_released = true;

//    if(_window){
//        ANativeWindow_release(_window);
//        _window = nullptr;
//    }
//    ResetAndroidView();

    LOGV("[EGL] egl error release ret=%0x\n", eglGetError());
    return 0;
}

int EglImpl::SetAndroidView(std::shared_ptr<JavaGlobalRef<jobject>> frame_window_ref){

    std::unique_lock<std::mutex> lck(_vout_mux);

    int ret = 0;

    EGLNativeWindowType window = nullptr;

    JNIEnv* env = AttachCurrentThreadIfNeeded();
    jobject surfaceview = frame_window_ref->operator*();
    LOGV("[EGL] SetAndroidView surfaceview %p\n", surfaceview);

    jclass cls = env->GetObjectClass(surfaceview);
    jmethodID mid_get_holder = env->GetMethodID(cls, "getHolder", "()Landroid/view/SurfaceHolder;");
    jobject surfaceholder = env->CallObjectMethod(surfaceview, mid_get_holder);

    jclass cls1 = env->GetObjectClass(surfaceholder);
    jmethodID mid_get_surface = env->GetMethodID(cls1, "getSurface", "()Landroid/view/Surface;");
    jobject surface = env->CallObjectMethod(surfaceholder, mid_get_surface);

    if(surface == nullptr){
        LOGE("[EGL] SetAndroidView surface is null\n");
        env->DeleteLocalRef(surface);
        env->DeleteLocalRef(cls);
        env->DeleteLocalRef(cls1);
        return -1;
    }

    LOGV("[EGL] SetAndroidView surface %p\n", surface);

    //check is surface valid
    //if surfaceview is invisible, surface is not valid
    jclass cls2 = env->GetObjectClass(surface);
    jmethodID mid_is_valid = env->GetMethodID(cls2, "isValid", "()Z");
    jboolean surface_valid = env->CallBooleanMethod(surface, mid_is_valid);
    if(!surface_valid){
        LOGW("[EGL] SetAndroidView surface is not valid\n");
        ret = -1;
        goto jni_release;
    }

    if(surface){
        window = ANativeWindow_fromSurface(env, surface);
        if(window == _window) {
            LOGV("[EGL] SetAndroidView surface widow eq\n");
            ret = 0;
            goto jni_release;
        }
    }
    LOGV("[EGL] SetAndroidView surface widow=%p _window=%p\n", window, _window);

//    if(_window){
//        ANativeWindow_release(_window);
//        _window = nullptr;
//    }

//    if (window) {
//        ANativeWindow_acquire(static_cast<ANativeWindow*>(window));
//    }
    this->_window = window;

    //the local window must be released there, otherwise the fd will leak and lead to create
    //egl context 0x3003
//    if (window) {
//        ANativeWindow_release(static_cast<ANativeWindow*>(window));
//        window = nullptr;
//    }

jni_release:
    env->DeleteLocalRef(surface);
    env->DeleteLocalRef(surfaceholder);
    env->DeleteLocalRef(cls);
    env->DeleteLocalRef(cls1);
    env->DeleteLocalRef(cls2);

    return ret;
}

int EglImpl::ResetAndroidView(){
    if(_window){
        ANativeWindow_release(_window);
        _window = nullptr;
    }
    return 0;
}

int EglImpl::CreateSurface(){

    LOGV("[EGL] CreateSurface debug1");

    if(!_window || !_display){
        LOGE("[EGL] CreateSurface _window=%p _display=%p", _window, _display);
        return -1;
    }

    LOGV("[EGL] CreateSurface debug2");

    static const EGLint configAttribs[] = {
            EGL_RENDERABLE_TYPE,    EGL_OPENGL_ES2_BIT,
            EGL_SURFACE_TYPE,       EGL_WINDOW_BIT,
            EGL_BLUE_SIZE,          8,
            EGL_GREEN_SIZE,         8,
            EGL_RED_SIZE,           8,
            EGL_NONE
    };

    EGLConfig config;
    EGLint numConfig;
    if (!eglChooseConfig(_display, configAttribs, &config, 1, &numConfig)) {
        LOGE("[EGL] CreateSurface eglChooseConfig failed\n");
        eglTerminate(_display);
        return -1;
    }

    LOGV("[EGL] CreateSurface debug3");

    EGLSurface surface = eglCreateWindowSurface(_display, config, _window, NULL);
    if (surface == EGL_NO_SURFACE) {
        LOGE("[EGL] CreateSurface eglCreateWindowSurface failed ret=%0x\n", eglGetError());
        eglTerminate(_display);
        return -1;
    }

    _surface = surface;
    LOGV("[EGL] CreateSurface debug4");

    return 0;
}

int EglImpl::DestorySurface(){

    if(_surface){
        eglDestroySurface(_display, _surface);
        _surface = EGL_NO_SURFACE;
    }

    if(_window){
        ANativeWindow_release(_window);
        _window = nullptr;
    }

    return 0;
}

int EglImpl::MakeCurrent(){

    if(IsEglValidate()){
        if (!eglMakeCurrent(_display, _surface, _surface, _context)) {
            LOGE("[EGL] elgMakeCurrent() failed (cached)\n");
            return -1;
        }
        //LOGV("[EGL] elgMakeCurrent ready\n");
        return 0;
    }

    int ret = InitGLEnv();
    if(ret != EGL_TRUE){
        LOGE("[AndroidEgl] init egl env error !!!\n");
        return -1;
    }
    return 0;
}

int EglImpl::EGLSwap(){

    int ret = eglSwapBuffers(_display, _surface);
    if(ret != EGL_TRUE){
        LOGE("[AndroidEgl] EGLSwap error ret=%d!!!\n", ret);
        return -1;
    }
    return 0;
}

int EglImpl::DetachCurrent(){

    int ret = eglMakeCurrent(_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
    if(ret != EGL_TRUE){
        LOGE("[AndroidEgl] eglMakeCurrent release error ret=%d!!!\n", ret);
    }
//    eglReleaseThread();
//    if(ret != EGL_TRUE){
//        LOGE("[AndroidEgl] eglReleaseThread release error ret=%d!!!\n", ret);
//    }
    return 0;
}

int EglImpl::SetSurfaceSize(int w, int h){

    if(!IsEglValidate()){
        return -1;
    }

#ifdef __ANDROID__
    _width = GetSurfaceWidth();
    _height = GetSurfaceHeight();
    if(_width != w || _height != h){
        //rescale surface
        int format = ANativeWindow_getFormat(_window);
        LOGV("ANativeWindow_setBuffersGeometry(w=%d,h=%d) -> (w=%d,h=%d)",
              _width, _height, w, h);
        int ret = ANativeWindow_setBuffersGeometry(_window, _width, _height, format);
        if (ret) {
            LOGE("[EGL] ANativeWindow_setBuffersGeometry() returned error %d", ret);
            return -1;
        }

        _width  = GetSurfaceWidth();
        _height = GetSurfaceHeight();
        return (_width && _height) ? 0 : -1;
    }
#endif
    return 0;
}

bool EglImpl::IsEglValidate(){
    LOGV("[EGL] IsEglValidate check enable _window=%p _display=%p _surface=%p _context=%p", _window, _display, _surface, _context);
    return _window && _display && _surface && _context;
}

bool EglImpl::HasContext(){
    LOGV("[EGL] HasContext check enable _context=%p", _context);
    return _context;
}

int EglImpl::GetSurfaceWidth(){

    EGLint width = 0;
    if (!eglQuerySurface(_display, _surface, EGL_WIDTH, &width)) {
        LOGE("[EGL] eglQuerySurface(EGL_WIDTH) returned error %d", eglGetError());
        return 0;
    }
    return width;
}

int EglImpl::GetSurfaceHeight(){

    EGLint height = 0;
    if (!eglQuerySurface(_display, _surface, EGL_HEIGHT, &height)) {
        LOGE("[EGL] eglQuerySurface(EGL_HEIGHT) returned error %d", eglGetError());
        return 0;
    }
    return height;
}

int EglImpl::GetWidth(){
    return _width;
}

int EglImpl::GetHeight(){
    return _height;
}

}